pp108 : Working with Process Event Listeners

Working with Process Event Listeners

This topic describes working with process event listeners.

Process event listeners enable developers to write custom business logic to persist business process information in custom repositories at various events of the business process execution. 

Business process information is published at the following events:

  • OnProcessStart
  • OnProcessComplete
  • OProcess Abort
  • OnActivityStart
  • OnActivityComplete
  • OnActivityAbort

Developers must implement the custom business logic as part of the event listener class and register the event listener class as part of the business process properties.

Process event listeners can be configured for a specific process. There can be different listener implementations for different process models or a single event listener for all the process models.

Creating process event listeners

To create a process event listener:

  1. Add the required logic in a Java class implementing the com.cordys.bpm.event.ProcessEventListener interface available in the bpmengine.jar found at <cordysInstallDir>/components/bpmengine. See the following ProcessEventListener contract.
 package com.cordys.bpm.event;
/**
 * Use this contract to execute your custom logic when a process event occurs for a process in Process Platform. 
 * Configure the FQN of the class implementing this interface in the process properties of the process model.
 * You can provide the implementation for the events that occur within a process instance life cycle.
 * 
 */
public interface ProcessEventListener
{
	/**
     * This method is invoked when a process instance is created.
     */
    void onProcessStart(ProcessInformation processInfo);
    
    /**
     * This method is invoked when the process instance is complete.
     */
    void onProcessComplete(ProcessInformation processInfo);
   
    /**
     * This method is invoked when an activity instance goes to an error state during its execution.
     */
    void onProcessAbort(ProcessInformation processInfo);
    
    /**
     * This method is invoked when an activity instance of a process is created.
     */
    void onActivityStart(ActivityInformation activityInfo);
    
    /**
     * This method is invoked when an activity instance of a process is complete.
     */
    void onActivityComplete(ActivityInformation activityInfo);
    
    /**
     * This method is invoked when an activity instance results in an error during execution.
     */
    void onActivityAbort(ActivityInformation activityInfo);
}
Parameter Classes
package com.cordys.bpm.event;

import javax.transaction.SystemException;
import javax.transaction.Transaction;
import org.w3c.dom.Element;
import com.eibus.localization.exception.custom.LocalizableIllegalStateException;
/**
 * This contract provides the APIs that are available to the event listeners as parameters.
 */
public interface ProcessInformation
{
    /**
     * This method returns the ID of the current instance.
     */
    String getInstanceID();
 
    /**
     * This method returns the parent instance from where the current instance is created.
     */
    String getParentID();
 
    /**
     * This method returns the root instance ID from where the current instance is created.
     */
    String getRootID();
 
    /**
     * This method returns the process model ID from where the current instance is created.
     */
    String getProcessModelID();
 
    /**
     * This method returns the organization DN under which the current instance is created.
     */
    String getOrganization();
 
    /**
     * This method returns the process model name whose instance is created.
     */
    String getProcessModelName();
 
    /**
     * This method returns the status of the current instance.
     */
    String getStatus();
 
    /**
     * This method returns the user DN of the user who last worked on the current instance.
     */
    String getCurrentOwner();
 
    /**
     * This method returns the start time of the current instance in milliseconds.
     */
    long getProcessStartTime();
 
    /**
     * This method returns the last updated time of the current instance in milliseconds.
     */
    long getProcessLastUpdatedTime();
 
    /**
     * This method returns the message map element of the current instance, which is updated with the input and output message of each activity in the process model.
     */
    Element getMessageMap();
 
    /**
     * This method returns the error text of the current instance if an error occurs.
     */
    String getErrorText();
}
 
package com.cordys.bpm.event;
/**
 * This contract provides the APIs that are available to the event listeners as parameters.
 */
public interface ActivityInformation
{
	/**
     * This method returns the activity ID.
     */
    String getActivityID();
    /**
     * This method returns the iteration count of the activity execution.
     */
    String getIterationCount();
    /**
     * This method returns true if the current activity execution is replaced.
     */
    boolean isReplaced();
    /**
     * This method returns the correlation ID of the created activity. If the activity is of the type task or Web service, the method returns the task or message ID.
     * If the activity type is a sub-process or sub-case, then the method returns instance ID of the sub-process or sub-case. 
     */
    String getCorrelationID();
    /**
     * This method returns the current activity name.
     */
    String getActivityName();
    /**
     * This method returns the current activity type.
     */
    String getActivityType();
    /**
     * This method returns the status of the current activity.
     */
    String getActivityStatus();
    /**
     * This method returns the start time of the current activity in milliseconds.
     */
    long getActivityStartTime();
    /**
     * This method returns the last updated time of the current activity in milliseconds. This value is available only after the activity is complete.
     */
    long getActivityEndTime();
    /**
     * This method returns the execution userDN of the current activity. This value is available only after the activity is complete.
     */
    String getExecutedBy();
    /**
     * This method returns the {{IProcessInformation}} object.
     */
    ProcessInformation getProcessInfo();
}

Note: The ProcessInformation and ActivityInformation class instances are provided as inputs to the listener methods that hold the process and activity details.

2. Create a Java archive of the process event listener implementation. For information about creating a JAR file, see  Creating a Java Archive Definition.

Note:

When configuring and implementing process event listeners, ensure the following:

  • The event listener implementation neither influences process engine execution nor modifies the business process runtime data. The exceptions thrown from the listener implementations are ignored by the process engine.
  • The event listener implementation must avoid resource intensive processing logic and reading from the Platform repositories to ensure there is no performance drop in the process execution.
  • Check for memory leaks, thread safe issues, errors, and so on in the event listener implementation before configuring them for the process models.

Configuring process event listeners

To configure a process event listener: 

  1. Add the absolute path of the JAR file containing the process event listener implementation classes to the Business Process Management service container class path. For more information, see the JRE section in the Service Container Properties Interface topic.
  2. On the  Properties  tab of  the process model, type the FQN (Fully Qualified Name) of the event listener implementation class.
  3. Publish the business process model to associate the process event listener to the process model.

Example

See the following sample code that demonstrates how to implement a Java class using ProcessEventListener and specify the custom logic in the methods. It uses atomikos distributed transaction manager and Active MQ for its logic.

 

TestProcessListener.java
package com.test;

import java.util.Properties;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.TextMessage;
import javax.jms.XAConnection;
import javax.jms.XAConnectionFactory;
import javax.jms.XASession;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAResource;
import org.apache.activemq.command.ActiveMQQueue;
import com.cordys.bpm.event.ActivityInformation;
import com.cordys.bpm.event.Transactional;
import com.cordys.bpm.event.ProcessEventListener;
import com.cordys.bpm.event.ProcessInformation;

public class TestProcessListener implements ProcessEventListener
{
	private ActiveMQQueue queue;
	private MessageProducer listenerQueue;
	private Context context;
	private XAConnectionFactory connectionFactory;
	private XASession session;
	
	private static final String TRANSACTION_MANAGER_FACTORY_CLASS = "com.cordys.xatransaction.atomikos.AtomikosTransactionManagerFactory";
	private static final String JNDI_FILE_PROPERTY_NAME = "com.cordys.transport.jms.jndifile";
	private static final String JNDI_FILE_PATH = System.getenv("CORDYS_HOME") + "configreliableMessaging.properties";
	
	private static final String CONTEXT_FACTORY_CLASS = "org.apache.activemq.jndi.ActiveMQInitialContextFactory";
	private static final String TRANSACTION_PROVIDER_URL = "tcp://localhost:61616";
	private static final String CONNECTION_FACTORY_NAME = "QueueConnectionFactory";
	private static final String LISTENER_QUEUE_NAME = "ListenerProcessQueue";
	
	public TestProcessListener() throws NotSupportedException, SystemException, NamingException, JMSException, IllegalStateException, RollbackException
	{
		// Setup transaction properties
		System.setProperty(TransactionManagerFactory.class.getCanonicalName(), TRANSACTION_MANAGER_FACTORY_CLASS);
		System.setProperty(JNDI_FILE_PROPERTY_NAME, JNDI_FILE_PATH);
	}
	
	private void enlistQueueResource() throws NotSupportedException, SystemException, NamingException, JMSException, IllegalStateException, RollbackException
	{
		// Create a JNDI API InitialContext object
    	Properties props = new Properties();
		props.setProperty(Context.INITIAL_CONTEXT_FACTORY, CONTEXT_FACTORY_CLASS);
		props.setProperty(Context.PROVIDER_URL, TRANSACTION_PROVIDER_URL);
		
        context = new InitialContext(props);
        queue = new ActiveMQQueue(LISTENER_QUEUE_NAME);
        
        connectionFactory = (ConnectionFactory) context.lookup(CONNECTION_FACTORY_NAME);
        Connection connection = connectionFactory.createXAConnection();
        connection.start();
        
        session = connection.createSession();
        listenerQueue = session.createProducer(queue);
	}
	
	private void addMessageToTransaction(String messageText)
	{
		try
		{
			if (listenerQueue == null)
			{
				enlistQueueResource();
			}
			TextMessage message = session.createTextMessage(messageText);
	        listenerQueue.send(message);
		}
		catch (JMSException | IllegalStateException | NotSupportedException | SystemException | NamingException | RollbackException e)
		{
			throw new RuntimeException(e);
		}
	}
	
	@Override
	public void onProcessStart(ProcessInformation processInfo)
	{
		try {
			addMessageToTransaction("MSG - PROCESS START : " + processInfo.getStatus());
		}
		catch (SystemException sysException)
		{
			throw new RuntimeException(sysException);
		}
	}
	@Override
	public void onProcessComplete(ProcessInformation processInfo)
	{
		try {
			addMessageToTransaction("MSG - PROCESS COMPLETED : " + processInfo.getStatus());
		}
		catch (SystemException sysException)
		{
			throw new RuntimeException(sysException);
		}
	}
	@Override
	public void onProcessAbort(ProcessInformation processInfo)
	{
		try {
			addMessageToTransaction("MSG - PROCESS ABORTED : " + processInfo.getStatus());
		}
		catch (SystemException sysException)
		{
			throw new RuntimeException(sysException);
		}
	}
	@Override
	public void onActivityStart(ActivityInformation activityInfo)
	{
		try {
			addMessageToTransaction("MSG - ACTIVITY START : " + activityInfo.getActivityStatus());
		}
		catch (SystemException sysException)
		{
			throw new RuntimeException(sysException);
		}
	}
	@Override
	public void onActivityComplete(ActivityInformation activityInfo)
	{
		try {
			addMessageToTransaction("MSG - ACTIVITY COMPLETED : " + activityInfo.getActivityStatus());
		}
		catch (SystemException sysException)
		{
			throw new RuntimeException(sysException);
		}
	}
	@Override
	public void onActivityAbort(ActivityInformation activityInfo)
	{
		try {
			addMessageToTransaction("MSG - ACTIVITY ABORTED : " + activityInfo.getActivityStatus());
		}
		catch (SystemException sysException)
		{
			throw new RuntimeException(sysException);
		}
	}
	
	public static void main(String[] args) throws Exception
	{
		TestProcessListener listenerTest = new TestProcessListener();
		listenerTest.addMessageToTransaction("Hello World!");
	}
}